package lambdasinaction.chap6;

import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;

import static lambdasinaction.chap6.Dish.menu;

/**
 * Created by ThinkPad on 2017/8/21.
 */
public class Grouping {
    enum CaloricLevel {DIET,NORMAL,FAT};

    public static void main(String... args) {
        System.out.println("Dishes grouped by type: " + groupDishByType());
        System.out.println("Dishes grouped by caloric level: " + groupDishByCaloricLevel());
        System.out.println("Dishes grouped by type and caloric level: " + groupDishByTypeAndCaloricLevel());
        System.out.println("Count dishes in groups: " + countDishInGroups());
        System.out.println("Most caloric dishes by type: " + mostCaloricDishesByType());
        System.out.println("Most caloric dishes by type: " + mostCaloricDishesByTypeWithoutOptionals());
        System.out.println("Sum calories by type: " + sumCaloriesByType());
        System.out.println("Caloric level by type: " + caloricLevelsByType());
    }

    private static Map<Dish.Type,List<Dish>> groupDishByType() {
        return menu.stream().collect(Collectors.groupingBy(Dish::getType));
    }

    private static Map<CaloricLevel,List<Dish>> groupDishByCaloricLevel() {
        return menu.stream().collect(
                Collectors.groupingBy(dish -> {
                    if (dish.getCalories() <= 400) {
                        return CaloricLevel.DIET;
                    } else if (dish.getCalories() <= 700) {
                        return CaloricLevel.NORMAL;
                    } else {
                        return CaloricLevel.FAT;
                    }
                })
        );
    }

    private static Map<Dish.Type,Map<CaloricLevel,List<Dish>>> groupDishByTypeAndCaloricLevel() {
        return menu.stream().collect(
                Collectors.groupingBy(Dish::getType,
                        Collectors.groupingBy((Dish dish) -> {
                            if (dish.getCalories() <= 400) return CaloricLevel.DIET;
                            else if (dish.getCalories() <= 700) return CaloricLevel.NORMAL;
                            else return CaloricLevel.FAT;
                        })
                )
        );
    }

    private static Map<Dish.Type, Long> countDishInGroups() {
        return menu.stream().collect(Collectors.groupingBy(
                Dish::getType,Collectors.counting()
        ));
    }

    private static Map<Dish.Type,Optional<Dish>> mostCaloricDishesByType() {
        return menu.stream().collect(Collectors
        .groupingBy(Dish::getType,Collectors.reducing(
                (Dish d1,Dish d2) -> d1.getCalories() > d2.getCalories() ? d1 : d2
        )));
    }

    private static Map<Dish.Type,Dish> mostCaloricDishesByTypeWithoutOptionals() {
        return menu.stream().collect(Collectors
        .groupingBy(Dish::getType,Collectors
        .collectingAndThen(Collectors
        .reducing((d1,d2) -> d1.getCalories()>d2.getCalories()?d1:d2),
                Optional::get)));
    }

    private static Map<Dish.Type,Integer> sumCaloriesByType() {
        return menu.stream().collect(Collectors
        .groupingBy(Dish::getType,Collectors
        .summingInt(Dish::getCalories)));
    }

    private static Map<Dish.Type,Set<CaloricLevel>> caloricLevelsByType() {
        return menu.stream().collect(Collectors
        .groupingBy(Dish::getType,Collectors
        .mapping(
                dish -> {
                    if (dish.getCalories() <= 400) return CaloricLevel.DIET;
                    else if (dish.getCalories() <= 700) return CaloricLevel.NORMAL;
                    else return CaloricLevel.FAT;
                },Collectors.toSet()
        )));
    }
}
